package data;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Observable;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class Download extends Observable implements Runnable {

	private static final int MAX_BUFFER_SIZE = 1024;

	public static final int DOWNLOADING = 0;
	public static final int PAUSED = 1;
	public static final int COMPLETE = 2;
	public static final int CANCELLED = 3;
	public static final int ERROR = 4;
	public static final int EXTRACTING = 5;

	public static final String STATUSES[] = { "Downloading", "Paused", "Complete", "Cancelled", "Error", "Extracting" };

	private URL url; // download URL
	private String saveDir; // dir to save
	private int size; // size of download in bytes
	private int downloaded; // number of bytes downloaded
	private int status;
	private long startTime = 0;
	public static final boolean proxyRequired = false;

	public static final String proxyIP = "127.0.0.1";
	public static final String proxyPort = "8080";
	public static final String proxyUsername = "proxyUser";
	public static final String proxyPassword = "proxyPassword";

	public Download(String url, String saveDir) {
		try {
			this.url = new URL(url);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
		this.saveDir = saveDir;
		size = -1;
		downloaded = 0;
		status = DOWNLOADING;
	}

	public String getUrl() {
		return url.toString();
	}

	public int getSize() {
		return size;
	}

	public float getProgress() {
		return (float) downloaded / size * 100;
	}

	public long getTimeRan() {
		return System.currentTimeMillis() - startTime;
	}

	public double getBytesPerSecond() {
		return (double) downloaded / getTimeRan() / 1000;
	}

	public double getKBPerSecond() {
		return (double) (downloaded / 1024) / (getTimeRan() / 1000);
	}

	public double getMBPerSecond() {
		return (double) downloaded / 1024 / 1024 / (getTimeRan() / 1000);
	}

	public double getGBPerSecond() {
		return (double) downloaded / 1024 / 1024 / 1024 / (getTimeRan() / 1000);
	}

	public long getEstimatedTimeLeft() {
		return (long) ((size - downloaded) / getBytesPerSecond() * 1000);
	}

	public int getStatus() {
		return status;
	}

	public void pause() {
		status = PAUSED;
		stateChanged();
	}

	public void resume() {
		status = DOWNLOADING;
		stateChanged();
		download();
	}

	public void cancel() {
		status = CANCELLED;
		stateChanged();
	}

	private void error() {
		status = ERROR;
		stateChanged();
	}

	private void extract() {
		if (getFileName(url).contains(".zip")) {
			status = EXTRACTING;
			stateChanged();
			try {
				ZipFile jar = new ZipFile(saveDir + File.separator + getFileName(url));
				Enumeration enume = jar.entries();
				while (enume.hasMoreElements()) {
					ZipEntry file = (ZipEntry) enume.nextElement();
					File f = new java.io.File(saveDir + File.separator + file.getName());
					if (file.isDirectory()) { // if its a directory, create it
						f.mkdir();
						continue;
					}
					InputStream is = jar.getInputStream(file);
					FileOutputStream fos = new FileOutputStream(f);
					while (is.available() > 0) {
						fos.write(is.read());
					}
					fos.close();
					is.close();
				}

				new File(saveDir + File.separator + getFileName(url)).delete();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public void download() {
		Thread thread = new Thread(this);
		thread.start();
	}

	// Get file name portion of URL.

	public String getFileName(URL url) {
		String fileName = url.getFile();
		return fileName.substring(fileName.lastIndexOf('/') + 1);
	}

	// Download file.
	@Override
	public void run() {
		RandomAccessFile file = null;
		InputStream stream = null;
		FileOutputStream out = null;

		File file2 = new File(saveDir + File.separator + getFileName(url));
		if (file2.exists()) {
			status = COMPLETE;
			return;
		}

		try {
			if (proxyRequired) {
				Properties systemSettings = System.getProperties();
				systemSettings.put("http.proxyHost", proxyIP);
				systemSettings.put("http.proxyPort", proxyPort);
				System.setProperties(systemSettings);
			}

			HttpURLConnection connection = (HttpURLConnection) url.openConnection();
			connection.setRequestProperty("Range", "bytes=" + downloaded + "-");
			if (proxyRequired) {
				// connection.setRequestProperty("Proxy-Authorization", "Basic "
				// // + encoded);
			}

			connection.connect();
			int responseCode = connection.getResponseCode();

			if (responseCode == 200 || responseCode == 206) {
				error();
			}

			int contentLength = connection.getContentLength();
			if (contentLength < 1) {
				error();
			}

			if (size == -1) {
				size = contentLength;
				stateChanged();
			}

			file = new RandomAccessFile(getFileName(url), "rw");
			file.seek(downloaded);
			stream = connection.getInputStream();
			status = DOWNLOADING;
			out = new FileOutputStream(saveDir + File.separator + getFileName(url));
			startTime = System.currentTimeMillis();

			while (status == DOWNLOADING) {
				byte buffer[];
				if (size - downloaded > MAX_BUFFER_SIZE) {
					buffer = new byte[MAX_BUFFER_SIZE];
				} else {
					buffer = new byte[size - downloaded];
				}

				int read = stream.read(buffer);
				if (read == -1) {
					break;
				}

				out.write(buffer, 0, read);
				downloaded += read;
				stateChanged();
			}

			stateChanged();
			if (status == DOWNLOADING) {
				extract();
				status = COMPLETE;
				stateChanged();
			}
		} catch (Exception e) {
			e.printStackTrace();
			error();
		} finally {
			if (file != null) {
				try {
					out.close();
					file.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}

			if (stream != null) {
				try {
					stream.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	private void stateChanged() {
		setChanged();
		notifyObservers();
	}
}
